1 module hip.assets.inputmap; 2 import hip.util.reflection; 3 import hip.data.jsonc; 4 import hip.error.handler; 5 import hip.api.input.inputmap; 6 import hip.api.data.asset; 7 import hip.api.input.core; 8 9 private enum Axes : ubyte 10 { 11 x = 0b1, 12 y = 0b10, 13 z = 0b11, 14 leftStick = 1 << 2, 15 rightStick = 1 << 3, 16 17 stickX = 0b01 << 4, 18 stickY = 0b10 << 4, 19 stickZ = 0b11 << 4 20 } 21 22 Axes getStickFromString(Axes baseAxis, string whichStick, string stickAxis) 23 { 24 Axes stick = whichStick == "left" ? Axes.leftStick : Axes.rightStick; 25 switch(stickAxis) 26 { 27 case "x": return cast(Axes)(Axes.stickX | baseAxis | stick); 28 case "y": return cast(Axes)(Axes.stickY | baseAxis | stick); 29 case "z": return cast(Axes)(Axes.stickZ | baseAxis | stick); 30 default: 31 assert(false, "Can only accept 'x', 'y', 'z' from getStick"); 32 } 33 } 34 35 36 37 38 39 class HipInputMap : HipAsset, IHipInputMap 40 { 41 alias Context = IHipInputMap.Context; 42 alias AxisContext = IHipInputMap.AxisContext; 43 44 Context[string] inputMapping; 45 46 AxisContext[][string] directionalsMapping; 47 ubyte id; 48 this() 49 { 50 super("HipInputMap"); 51 } 52 //registerInputAction("menu", MOUSE_BTN_R, TOUCH_0 | TOUCH_1, KEY_WINDOWS) 53 private pragma(inline) Context* getAction(string actionName) 54 { 55 Context* ctx = actionName in inputMapping; 56 if(ctx == null) 57 { 58 ErrorHandler.showErrorMessage("HipInputMap action getter error", 59 '"'~actionName~"' does not exists on input mapping"); 60 } 61 return ctx; 62 } 63 private pragma(inline) AxisContext[] getDirectional(string directionalName) 64 { 65 AxisContext[]* ctx = directionalName in directionalsMapping; 66 if(ctx == null) 67 { 68 ErrorHandler.showErrorMessage("HipInputMap directionals getter error", 69 '"'~directionalName~"' does not exists on direction mapping"); 70 } 71 return *ctx; 72 } 73 74 float isActionPressed(string actionName) 75 { 76 Context* c = getAction(actionName); 77 if(!c) return 0.0f; 78 float greatest = 0; 79 foreach(g; c.btns) if(HipInput.isGamepadButtonPressed(g, id)) 80 greatest = 1.0f; 81 foreach(k; c.keys) if(HipInput.isKeyPressed(k, id)) 82 greatest = 1.0f; 83 return greatest; 84 } 85 float isActionJustPressed(string actionName) 86 { 87 Context* c = getAction(actionName); 88 if(!c) return 0.0f; 89 float greatest = 0; 90 foreach(g; c.btns) if(HipInput.isGamepadButtonJustPressed(g, id)) 91 greatest = 1.0f; 92 foreach(k; c.keys) if(HipInput.isKeyJustPressed(k, id)) 93 greatest = 1.0f; 94 return greatest; 95 } 96 float isActionJustReleased(string actionName) 97 { 98 Context* c = getAction(actionName); 99 if(!c) return 0.0f; 100 float greatest = 0; 101 foreach(g; c.btns) if(HipInput.isGamepadButtonJustReleased(g, id)) 102 greatest = 1.0f; 103 foreach(k; c.keys) if(HipInput.isKeyJustReleased(k, id)) 104 greatest = 1.0f; 105 return greatest; 106 } 107 108 109 Vector3 getAxis(string directionalName) 110 { 111 AxisContext[] axisArray = getDirectional(directionalName); 112 Vector3 ret; 113 foreach(AxisContext ax; axisArray) 114 { 115 int i = 0; 116 switch(ax.axis & Axes.z) 117 { 118 case Axes.x: break; 119 case Axes.y: i = 1; break; 120 case Axes.z: i = 2; break; 121 default: 122 throw new Exception("Invalid Axis context found."); 123 } 124 if(ax.axis & Axes.leftStick || ax.axis & Axes.rightStick) 125 { 126 ret[i]+= HipInput.getAnalog((ax.axis & Axes.leftStick) != 0 ? HipGamepadAnalogs.leftStick : HipGamepadAnalogs.rightStick, id)[i]; 127 continue; 128 } 129 if(HipInput.isGamepadButtonPressed(ax.btn, id) || HipInput.isKeyPressed(ax.key, id)) 130 ret[i]+= (cast(float)ax.value / 127.0); 131 } 132 return ret; 133 } 134 135 void registerInputAction(string actionName, Context ctx) 136 { 137 mixin(ErrorHandler.assertReturn!q{actionName != ""}("Register Input Action should contain a name")); 138 inputMapping[actionName] = ctx; 139 } 140 override void onFinishLoading() {} 141 override bool isReady() const { return inputMapping !is null; } 142 override void onDispose() {} 143 144 static HipInputMap parseInputMap(const ubyte[] file, string fileName, ubyte id = 0) 145 { 146 import hip.util.exception; 147 HipInputMap ret = new HipInputMap(); 148 JSONValue inputJson = parseJSON(cast(string)file); 149 enforce(inputJson.type == JSONType.object, "Input map at path "~fileName~" must be an object"); 150 151 //Parse "actions" 152 JSONValue* actions = ("actions" in inputJson); 153 if(actions) 154 { 155 foreach(k, v; actions.object) 156 { 157 string actionName = k; 158 JSONValue* kb = ("keyboard" in v.object); 159 JSONValue* gp = ("gamepad" in v.object); 160 161 Context ctx; 162 if(kb != null) 163 { 164 foreach(key; kb.array) //Keyboard 165 ctx.keys~= key.str[0]; 166 } 167 if(gp != null) 168 { 169 foreach(btn; gp.array) //Gamepad 170 ctx.btns ~= gamepadButtonFromString(btn.str); 171 } 172 ret.inputMapping[actionName] = ctx; 173 ctx.name = actionName; 174 } 175 } 176 JSONValue* directionals = "directionals" in inputJson; 177 if(directionals) 178 { 179 enforce(directionals.type == JSONType.object, "Directionals must be an object."); 180 foreach(string directionalName, JSONValue dV; directionals.object) 181 { 182 enforce(dV.type == JSONType.object, "Directionals must hold an object."); 183 foreach(string axis, JSONValue content; dV) 184 { 185 AxisContext axisCtx; 186 switch(axis) 187 { 188 case "x": 189 axisCtx.axis = Axes.x; 190 break; 191 case "y": 192 axisCtx.axis = Axes.y; 193 break; 194 case "z": 195 axisCtx.axis = Axes.z; 196 break; 197 default: 198 enforce(false, "Directional named "~directionalName~" must hold an object named 'x', 'y' or 'z'"); 199 } 200 enforce(content.type == JSONType.array, "Axis '"~axis~"' of directional '"~directionalName~"' must hold an array."); 201 foreach(JSONValue value; content.array) 202 { 203 AxisContext newAxis = axisCtx; 204 enforce(value.type == JSONType.object, "Axis '"~axis~"' of directional '"~directionalName~"' can only contain an array of objects."); 205 206 JSONValue* an = "analog" in value; 207 JSONValue* kb = "keyboard" in value; 208 JSONValue* gp = "gamepad" in value; 209 JSONValue* v = "value" in value; 210 211 if(kb || gp) 212 { 213 enforce(!an, "If your input has either a keyboard or gamepad, it can't have an analog."); 214 if(kb) 215 { 216 enforce(!an, "Keyboard can only receive a string."); 217 enforce(kb.type == JSONType..string, "Keyboard can only receive a string."); 218 newAxis.key = kb.str[0]; 219 } 220 if(gp) 221 { 222 enforce(gp.type == JSONType..string, "Gamepad can only receive a string."); 223 newAxis.btn = gamepadButtonFromString(gp.str); 224 } 225 enforce(v && (v.type == JSONType.integer || v.type == JSONType.float_) && (v.floating >= -1.0 && v.floating <= 1.0), "directionals input must have a value and it must be a float between -1 and 1"); 226 newAxis.value = cast(byte)((cast(int)v.floating) * 127); 227 } 228 else if(an) 229 { 230 enforce(!kb && !gp, "If your input has an analog, it can't contain a gamepad or keyboard"); 231 enforce(an.type == JSONType..string && (an.str == "left" || an.str == "right"), "Your analog must map to either 'left' or 'right'"); 232 JSONValue* stickAxis = "axis" in value; 233 enforce(stickAxis && stickAxis.type == JSONType..string, "An analog must have an axis, and its type must be a string"); 234 newAxis.axis = getStickFromString(cast(Axes)axisCtx.axis, an.str, stickAxis.str); 235 } 236 237 ret.directionalsMapping[directionalName]~= newAxis; 238 } 239 240 } 241 } 242 } 243 244 return ret; 245 } 246 }